package gui;

import java.io.*;
import java.util.*;
import java.math.*;



public class doLZW_decompress {


    private int paddedsize;
    private FileInputStream in;
    private String inputfile;
    private String dictionary_outputfile;
    private String decodedfile;
    private Vector key_dictionary;
    private Vector nonpadded_value_dictionary;
    private Vector value_dictionary;
    private Vector present_absent;
    private Vector decoded;
    private File file;
    private File dict_file;
    private File dec_file;
    private long StartTime;
    private long StopTime;
    private long FullTime;
    private BitInputStream bitin;
    private FileInputStream _inputStream;

    public void doDecompression(String args) {
        int filesize = 0;
        inputfile = args;
        try {
            file = new File(inputfile);

            if (!file.exists()) {
                System.out.println("\nFile : \"" + inputfile + "\" does not exist");
                System.exit(0);
            }
            filesize = (int) file.length();
            int codefilesize = filesize * 8;

            System.out.println("\nCompressed file : \"" + file.getName() + "\" (" + filesize + " bytes)");

            starttime();
            dictionary_outputfile = inputfile + ".dct";
            decodedfile = inputfile.substring(0, inputfile.length() - 4);

            present_absent = new Vector(3000, 100);
            key_dictionary = new Vector(3000, 100);
            nonpadded_value_dictionary = new Vector(3000, 100);
            value_dictionary = new Vector(3000, 100);
            initialize_key_Dictionary(key_dictionary);
            initialize_value_Dictionary(nonpadded_value_dictionary);
            initialize_value_Dictionary(value_dictionary);
            initialize_present_absent(present_absent);

            decoded = new Vector(3000, 100);//initialize the decoded vector

            doLZW(inputfile, dictionary_outputfile, decodedfile, codefilesize);

            //the following is used to output information about the compression results
            dec_file = new File(decodedfile);

            if (!dec_file.exists()) {
                System.out.println("\nDecoded file : \"" + decodedfile + "\" does not exist");
                System.exit(0);
            }
            int dec_filesize = 0;
            dec_filesize = (int) dec_file.length();
            System.out.println("Decompressed file : \"" + dec_file.getName() + "\" (" + dec_filesize + " bytes)");
            dict_file = new File(dictionary_outputfile);
            if (!dict_file.exists()) {
                System.out.println("\nDictionary File  : \"" + dictionary_outputfile + "\" was not created");
                System.exit(0);
            }
            int dict_filesize = 0;
            dict_filesize = (int) dict_file.length();
            System.out.println("Decompression Dictionary file : \n" + "\"" + dictionary_outputfile + "\" (" + dict_filesize + " bytes)");
            System.out.println("Number of entries in Dictionary : " + value_dictionary.size());
            System.out.println("Minimum code size : " + "7");
            String max_code_size = Integer.toBinaryString(value_dictionary.size());
            int int_max_code_size = max_code_size.length();
            System.out.println("Maximum code size : " + int_max_code_size);
            stoptime();//used for timer

        } catch (NullPointerException e) {
        }
    }//end doDecompression

    public void doLZW(String inputfile, String dictionary_outputfile, String decodedfile, int codefilesize) {

        try {
            _inputStream = new FileInputStream(inputfile);
            bitin = new BitInputStream(_inputStream);

            int _bit = 0;//used to store the bits

            String dictionary_size = Integer.toBinaryString(value_dictionary.size());
            int int_dictionary_size = dictionary_size.length();

            int _index = int_dictionary_size;
            int start_index = 0;
            int code_size = 0;
            code_size = int_dictionary_size;
            String current_character = new String();
            String new_character = new String();
            String new_string = new String();
            String previous_string = new String();

            while (_index < codefilesize + code_size) {

                String first = "";//stores the binary data read in

                if (_index <= codefilesize) {
                    for (int g = 0; g < _index - start_index; g++) {
                        _bit = bitin.readBit();
                        if (_bit != -1) {
                            first = first.concat(Integer.toString(_bit));
                        }
                    }
                } else {
                    break;
                }

                if (value_dictionary.contains((String) first))
                {

                    int index_of_char = value_dictionary.indexOf((String) first);
                    current_character = key_dictionary.elementAt(index_of_char).toString();
                    decoded.addElement(current_character);
                    
                    new_string = new_string + current_character.substring(0, 1);

                    if (!key_dictionary.contains((String) new_string)) //add to dictionary and pad dictionary
                    {
                        String value_next_binary = Integer.toBinaryString(value_dictionary.size());

                        key_dictionary.addElement(new_string);
                        value_dictionary.addElement(value_next_binary);
                        nonpadded_value_dictionary.addElement(value_next_binary);
                        value_next_binary = Integer.toBinaryString(value_dictionary.size());
                        present_absent.addElement("PRESENT");//value was present in dictionary

                        int string_length = value_next_binary.length();
                        if (paddedsize < string_length) {
                            pad_value_dictionary(value_dictionary, string_length);
                        }
                        code_size = string_length;
                        previous_string = new_string;


                        new_string = current_character;


                    }


                } else 
                {
                    //last + first char of last
                    String to_be_added = current_character + current_character.substring(0, 1);

                    //find next binary value
                    String value_next_binary = Integer.toBinaryString(value_dictionary.size());

                    //add value to decoded vector
                    decoded.addElement(to_be_added);

                    //add value to dictionary
                    key_dictionary.addElement(to_be_added);

                    //add corresponding binary value to dictionary
                    value_dictionary.addElement(value_next_binary);
                    nonpadded_value_dictionary.addElement(value_next_binary);

                    present_absent.addElement("ABSENT");//add present to dictionary

                    int string_length = value_next_binary.length();


                    if (paddedsize < string_length) {
                        pad_value_dictionary(value_dictionary, string_length);
                    }

                    code_size = string_length;

                    new_string = to_be_added;

                    current_character = to_be_added;

                }


                start_index = _index;
                _index = _index + code_size;

            } //end while

            bitin.close();
            _inputStream.close();

            //save the dictionary to the dictionary output file
            saveDictionary(key_dictionary, nonpadded_value_dictionary, present_absent, dictionary_outputfile);

            //save the deccompressed to the decompressed file
            saveDecoded(decoded, decodedfile);


        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }//end catch
    }

    private void pad_value_dictionary(Vector value_dictionary, int value_length) {

        for (int i = 0; i < value_dictionary.size(); i++) {
            String binary = value_dictionary.elementAt(i).toString();
            while (binary.length() < value_length) {
                binary = "0" + binary;//add zeros to the start of the codeword

            }
            value_dictionary.setElementAt(binary, i);
        }//end for

        paddedsize = value_length;
    }

    private void initialize_key_Dictionary(Vector key_dictionary) {

        for (int i = 0; i < 127; i++) {
            Character _char = new Character((char) i);
            String string_character = _char.toString();

            key_dictionary.addElement(string_character);

        }
    }

    private void initialize_value_Dictionary(Vector value_dictionary) {
        int int_binaryvalue = -1;
        String string_binaryvalue = "";

        for (int i = 0; i < 127; i++) {
            int_binaryvalue = int_binaryvalue + 1;
            string_binaryvalue = Integer.toBinaryString(int_binaryvalue);
            value_dictionary.addElement(string_binaryvalue);

        }
        String _binary = Integer.toBinaryString(value_dictionary.size());
        int key_length = _binary.length();
        pad_value_dictionary(value_dictionary, key_length);

    }
    private void initialize_present_absent(Vector present_absent) {
        for (int i = 0; i < 127; i++) {
            present_absent.addElement("PRESENT");
        }
    }

    private void saveDictionary(Vector key_dictionary, Vector value_dictionary, Vector present_absent, String dictionary_outputfile) {
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(dictionary_outputfile));
            for (int i = 0; i < key_dictionary.size(); i++) {

                try {

                    out.write((value_dictionary.elementAt(i).toString()) + "\t\t" + key_dictionary.elementAt(i).toString() + "\t\t\t" + present_absent.elementAt(i).toString() + "\n");

                } catch (IOException e) {
                    //
                }
            }//end for

            out.close();

        } catch (IOException e) {
            //
        }
    }


    private void saveDecoded(Vector decoded, String decodedfile) {
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(decodedfile));

            for (int i = 0; i < decoded.size(); i++) {
                try {
                    out.write(decoded.elementAt(i).toString());

                } catch (IOException e) {
                    //
                }
            }

            out.close();

        } catch (IOException e) {
            //
        }
    }//end save decoded

    public void starttime() {
        StartTime = System.currentTimeMillis();

    }

//used for the timig of the program
    public void stoptime() {
        StopTime = System.currentTimeMillis();
        FullTime = StopTime - StartTime;
        System.out.println("Running Time : " + FullTime + " ms");

    }
}//end class

class BitInputStream {

    private InputStream in;
    private int buffer;
    private int nextBit = 8;
//constructor

    public BitInputStream(InputStream in) {
        this.in = in;
    }
//reads the bit from the InputStream that was initialized in the constructor

    synchronized public int readBit() throws IOException {
        if (in == null) {
            throw new IOException("Already closed");
        }
        if (nextBit == 8) {
            buffer = in.read();

            if (buffer == -1) {
                return -1;
            }
            nextBit = 0;
        }

        int bit = buffer & (1 << nextBit);
        nextBit++;

        bit = bit == 0 ? 0 : 1;

        return bit;
    }

    public void close() throws IOException {
        in.close();
        in = null;
    }
}
